// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_POINTER_WITH_PAYLOAD_H_
#define V8_POINTER_WITH_PAYLOAD_H_

#include <cstdint>
#include <type_traits>

#include "include/v8config.h"
#include "src/base/logging.h"

namespace v8 {
namespace internal {

    template <typename PointerType>
    struct PointerWithPayloadTraits {
        static constexpr int value = alignof(PointerType) >= 8 ? 3 : alignof(PointerType) >= 4 ? 2 : 1;
    };

    // PointerWithPayload combines a PointerType* an a small PayloadType into
    // one. The bits of the storage type get packed into the lower bits of the
    // pointer that are free due to alignment. The user needs to specify how many
    // bits are needed to store the PayloadType, allowing Types that by default are
    // larger to be stored.
    //
    // Example:
    //   PointerWithPayload<int *, bool, 1> data_and_flag;
    //
    //   Here we store a bool that needs 1 bit of storage state into the lower bits
    //   of int *, which points to some int data;

    template <typename PointerType, typename PayloadType, int NumPayloadBits>
    class PointerWithPayload {
        // We have log2(ptr alignment) kAvailBits free to use
        static constexpr int kAvailBits = PointerWithPayloadTraits<
            typename std::remove_const<PointerType>::type>::value;
        static_assert(
            kAvailBits >= NumPayloadBits,
            "Ptr does not have sufficient alignment for the selected amount of "
            "storage bits.");

        static constexpr uintptr_t kPayloadMask = (uintptr_t { 1 } << kAvailBits) - 1;
        static constexpr uintptr_t kPointerMask = ~kPayloadMask;

    public:
        PointerWithPayload() { }

        explicit PointerWithPayload(PointerType* pointer)
            : pointer_(reinterpret_cast<uintptr_t>(pointer))
        {
            DCHECK_EQ(GetPointer(), pointer);
            DCHECK_EQ(GetPayload(), static_cast<PayloadType>(0));
        }

        explicit PointerWithPayload(PayloadType payload)
            : pointer_(static_cast<uintptr_t>(payload))
        {
            DCHECK_EQ(GetPointer(), nullptr);
            DCHECK_EQ(GetPayload(), payload);
        }

        PointerWithPayload(PointerType* pointer, PayloadType payload)
        {
            update(pointer, payload);
        }

        V8_INLINE PointerType* GetPointer() const
        {
            return reinterpret_cast<PointerType*>(pointer_ & kPointerMask);
        }

        V8_INLINE PointerType* operator->() const { return GetPointer(); }

        V8_INLINE void update(PointerType* new_pointer, PayloadType new_payload)
        {
            pointer_ = reinterpret_cast<uintptr_t>(new_pointer) | static_cast<uintptr_t>(new_payload);
            DCHECK_EQ(GetPayload(), new_payload);
            DCHECK_EQ(GetPointer(), new_pointer);
        }

        V8_INLINE void SetPointer(PointerType* newptr)
        {
            DCHECK_EQ(reinterpret_cast<uintptr_t>(newptr) & kPayloadMask, 0);
            pointer_ = reinterpret_cast<uintptr_t>(newptr) | (pointer_ & kPayloadMask);
            DCHECK_EQ(GetPointer(), newptr);
        }

        V8_INLINE PayloadType GetPayload() const
        {
            return static_cast<PayloadType>(pointer_ & kPayloadMask);
        }

        V8_INLINE void SetPayload(PayloadType new_payload)
        {
            uintptr_t new_payload_ptr = static_cast<uintptr_t>(new_payload);
            DCHECK_EQ(new_payload_ptr & kPayloadMask, new_payload_ptr);
            pointer_ = (pointer_ & kPointerMask) | new_payload_ptr;
            DCHECK_EQ(GetPayload(), new_payload);
        }

    private:
        uintptr_t pointer_ = 0;
    };

} // namespace internal
} // namespace v8

#endif // V8_POINTER_WITH_PAYLOAD_H_
